package com.nageoffer.shortlink.admin.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.nageoffer.shortlink.admin.dao.entity.User;
import com.nageoffer.shortlink.admin.dao.mapper.UserMapper;
import com.nageoffer.shortlink.common.dto.admin.req.UserLoginReqDTO;
import com.nageoffer.shortlink.common.dto.admin.req.UserRegisterReqDTO;
import com.nageoffer.shortlink.common.dto.admin.req.UserUpdateReqDTO;
import com.nageoffer.shortlink.common.dto.admin.resp.UserGetRespDTO;
import com.nageoffer.shortlink.common.dto.admin.resp.UserLoginRespDTO;
import com.nageoffer.shortlink.admin.service.GroupService;
import com.nageoffer.shortlink.admin.service.UserService;
import com.nageoffer.shortlink.common.dto.admin.biz.UserContext;
import com.nageoffer.shortlink.common.constant.RedisConstant;
import com.nageoffer.shortlink.common.convention.enums.UserErrorCodeEnum;
import com.nageoffer.shortlink.common.convention.exception.ClientException;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Map;

@RequiredArgsConstructor
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    private final RBloomFilter<String> userRegisterCachePenetrationBloomFilter;
    private final RedissonClient redissonClient;
    private final StringRedisTemplate stringRedisTemplate;
    private final GroupService groupService;
    @Override
    public UserGetRespDTO getUserByName(String username) {
        User user = lambdaQuery().eq(User::getUsername, username).one();
        if (user==null) {
            throw new ClientException(UserErrorCodeEnum.USER_NULL);
        }
        return BeanUtil.copyProperties(user, UserGetRespDTO.class);
    }

    @Override
    public boolean hasUsername(String username) {
        return userRegisterCachePenetrationBloomFilter.contains(username);
    }

    @Override
    public void register(UserRegisterReqDTO requestParam) {
        String username = requestParam.getUsername();
        if (hasUsername(username)) {
            throw new ClientException(UserErrorCodeEnum.USER_NAME_EXIST);
        }
        //使用redission分布式锁防止用户高并发创建同一个不存在的用户
        RLock lock = redissonClient.getLock(RedisConstant.LOCK_USER_REGISTER_KEY+username);
        try {
            if (lock.tryLock()) {
                try{
                    boolean save = save(BeanUtil.toBean(requestParam, User.class));
                    if (!save) {
                        throw new ClientException(UserErrorCodeEnum.USER_SAVE_ERROR);
                    }
                    groupService.saveGroup("默认分组",username);
                }catch (DuplicateKeyException ex){
                    userRegisterCachePenetrationBloomFilter.add(username);
                    throw new ClientException(UserErrorCodeEnum.USER_NAME_EXIST);
                }
                userRegisterCachePenetrationBloomFilter.add(username);
            }else {
                throw new ClientException("注册频繁");
            }
        }finally {
            lock.unlock();
        }
    }

    @Override
    public void updateInfo(UserUpdateReqDTO requestParam) {
        if (!requestParam.getUsername().equals(UserContext.getUsername())) {
            throw new ClientException(UserErrorCodeEnum.USER_MODIFY_ERROR);
        }
        User user = BeanUtil.toBean(requestParam, User.class);
        boolean update = lambdaUpdate().eq(User::getUsername, user.getUsername()).update(user);
        if (!update) {
            throw new ClientException(UserErrorCodeEnum.USER_MODIFY_ERROR);
        }
    }

    @Override
    public UserLoginRespDTO login(UserLoginReqDTO requestParam) {
        String username = requestParam.getUsername();
        String password = requestParam.getPassword();
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(RedisConstant.USER_LOGIN_KEY+ username);
        if (CollectionUtil.isNotEmpty(entries)) {
            String token = entries.keySet()
                    .stream()
                    .findFirst()
                    .map(Object::toString)
                    .orElseThrow(() -> new ClientException(UserErrorCodeEnum.USER_LOGIN_ERROR));
            return new UserLoginRespDTO(token);
        }
        User user = lambdaQuery().eq(User::getUsername, username)
                .eq(User::getPassword, password)
                .one();
        if (user==null) {
            throw new ClientException(UserErrorCodeEnum.USER_LOGIN_ERROR);
        }
        String uuid = UUID.randomUUID().toString();
        stringRedisTemplate.opsForHash().put(RedisConstant.USER_LOGIN_KEY+username,uuid, JSONUtil.toJsonStr(user));
        return new UserLoginRespDTO(uuid);
    }

    @Override
    public boolean checkLogin(String username, String token) {
        return stringRedisTemplate.opsForHash().get(RedisConstant.USER_LOGIN_KEY+username,token)!=null;
    }

    @Override
    public void logout(String username, String token) {
        if (checkLogin(username,token)) {
            stringRedisTemplate.delete(RedisConstant.USER_LOGIN_KEY+username);
        }
    }

    @Override
    public User getUserByUsername(String username) {
        return lambdaQuery().eq(User::getUsername,username)
                .one();
    }


}
